﻿/**
  @file
  @author Stefan Frings
*/

#include "JZWebResponse.h"


HttpResponse::HttpResponse()
{
    statusCode = 200;
    statusText = "OK";
    sentHeaders = false;
    sentLastPart = false;
    chunkedMode = false;
}

HttpResponse::~HttpResponse()
{

}

QByteArray HttpResponse::data()
{
    return m_buffer;
}

void HttpResponse::setHeader(QByteArray name, QByteArray value)
{
    Q_ASSERT(sentHeaders == false);
    headers.insert(name, value);
}

void HttpResponse::setHeader(QByteArray name, int value)
{
    Q_ASSERT(sentHeaders == false);
    headers.insert(name, QByteArray::number(value));
}

QMap<QByteArray, QByteArray>& HttpResponse::getHeaders()
{
    return headers;
}

void HttpResponse::setStatus(int statusCode, QByteArray description)
{
    this->statusCode = statusCode;
    statusText = description;
}

int HttpResponse::getStatusCode() const
{
    return this->statusCode;
}

void HttpResponse::writeHeaders()
{
    Q_ASSERT(sentHeaders == false);
    QByteArray buffer;
    buffer.append("HTTP/1.1 ");
    buffer.append(QByteArray::number(statusCode));
    buffer.append(' ');
    buffer.append(statusText);
    buffer.append("\r\n");
    foreach(QByteArray name, headers.keys())
    {
        buffer.append(name);
        buffer.append(": ");
        buffer.append(headers.value(name));
        buffer.append("\r\n");
    }
    foreach(HttpCookie cookie, cookies.values())
    {
        buffer.append("Set-Cookie: ");
        buffer.append(cookie.toByteArray());
        buffer.append("\r\n");
    }
    buffer.append("\r\n");
    writeToSocket(buffer);
    sentHeaders = true;
}

bool HttpResponse::writeToSocket(QByteArray data)
{
    m_buffer.append(data);
    return true;
}

void HttpResponse::write(QByteArray data, bool lastPart)
{
    Q_ASSERT(sentLastPart == false);

    // Send HTTP headers, if not already done (that happens only on the first call to write())
    if (sentHeaders == false)
    {
        // If the whole response is generated with a single call to write(), then we know the total
        // size of the response and therefore can set the Content-Length header automatically.
        if (lastPart)
        {
            // Automatically set the Content-Length header
            headers.insert("Content-Length", QByteArray::number(data.size()));
        }

        // else if we will not close the connection at the end and there is no Content-Length header,
        // then we must use the chunked mode.
        else
        {
            QByteArray connectionValue = headers.value("Connection", headers.value("connection"));
            bool connectionClose = QString::compare(connectionValue, "close", Qt::CaseInsensitive) == 0;
            if (!connectionClose && !headers.contains("Content-Length"))
            {
                headers.insert("Transfer-Encoding", "chunked");
                chunkedMode = true;
            }
        }

        writeHeaders();
    }

    // Send data
    if (data.size() > 0)
    {
        if (chunkedMode)
        {
            if (data.size() > 0)
            {
                QByteArray size = QByteArray::number(data.size(), 16);
                writeToSocket(size);
                writeToSocket("\r\n");
                writeToSocket(data);
                writeToSocket("\r\n");
            }
        }
        else
        {
            writeToSocket(data);
        }
    }

    // Only for the last chunk, send the terminating marker and flush the buffer.
    if (lastPart)
    {
        if (chunkedMode)
        {
            writeToSocket("0\r\n\r\n");
        }
        sentLastPart = true;
    }
}


bool HttpResponse::hasSentLastPart() const
{
    return sentLastPart;
}


void HttpResponse::setCookie(const HttpCookie& cookie)
{
    Q_ASSERT(sentHeaders == false);
    if (!cookie.getName().isEmpty())
    {
        cookies.insert(cookie.getName(), cookie);
    }
}


QMap<QByteArray, HttpCookie>& HttpResponse::getCookies()
{
    return cookies;
}


void HttpResponse::redirect(const QByteArray& url)
{
    setStatus(303, "See Other");
    setHeader("Location", url);
    write("Redirect", true);
}